﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;

namespace Atha.Clients.ViewModels
{
	public class MainViewModel : NotificationViewModelBase, IDisposable
	{
		private const string razor = "Razor";

		private const string ironPython = "IronPython";

		private const string ironRuby = "IronRuby";

		private const string powerShell = "PowerShell";

		private readonly Func<ITestScriptsReader> _testScriptsReaderFactory;

		private readonly Func<string, ITestScriptReader> _testScriptReaderFactory;

		private readonly Func<ITestScriptWriter> _testScriptWriterFactory;

		private readonly IClientTestRunner _clientTestRunner;

		private readonly Func<string> _getExistingTestPath;

		private readonly Func<string> _getNewTestSaveAsPath;

		private readonly IEnumerable<IDisposable> _observables;

		private ITestScript _selectedTestScript;

		private string _savedFilePath;

		private bool _isTutorialVisible;

		private string _testScript;

		private CommandViewModel _selectedLanguage;

		/// <summary>
		/// Initializes a new instance of the <see cref="MainViewModel"/> class.
		/// </summary>
		/// <param name="testScriptsReaderFactory">The test scripts reader factory.</param>
		/// <param name="testScriptReaderFactory">The test script reader factory.</param>
		/// <param name="testScriptWriterFactory">The test script writer factory.</param>
		/// <param name="clientTestRunner">The client test runner.</param>
		/// <param name="onNotify">The on notify.</param>
		/// <param name="getExistingTestPath">The get existing test path.</param>
		/// <param name="getNewTestSaveAsPath">The get new test save as path.</param>
		public MainViewModel(
			Func<ITestScriptsReader> testScriptsReaderFactory,
			Func<string, ITestScriptReader> testScriptReaderFactory,
			Func<ITestScriptWriter> testScriptWriterFactory,
			IClientTestRunner clientTestRunner,
			Action<NotifyViewModel> onNotify,
			Func<string> getExistingTestPath,
			Func<string> getNewTestSaveAsPath)
			: base(onNotify)
		{
			// TODO: Arg validations
			this._testScriptsReaderFactory = testScriptsReaderFactory;
			this._testScriptReaderFactory = testScriptReaderFactory;
			this._testScriptWriterFactory = testScriptWriterFactory;
			this._clientTestRunner = clientTestRunner;
			this._getExistingTestPath = getExistingTestPath;
			this._getNewTestSaveAsPath = getNewTestSaveAsPath;

			this.TestScripts = new ObservableCollection<ITestScript>();

			this.TestResults = new ObservableCollection<TestResultViewModel>();

			this.LearnAtha = new CommandViewModel("Learn Atha", @"Images\1322868576_school.png", true, this.OnLearnAtha);
			this.OpenTests = new CommandViewModel("Open Tests", @"Images\fileopen.png", true, this.OnOpenTests);
			this.OpenTest = new CommandViewModel("Open Script", @"Images\fileopen.png", true, this.OnOpenTest);
			this.SaveTest = new CommandViewModel("Save Script", @"Images\1321920923_3floppy_unmount.png", false, this.OnSaveTest);
			this.ExecuteTest = new CommandViewModel("Execute Script", @"Images\1321920940_application-x-executable-script.png", false, this.OnExecuteTest);

			var languages = new[]
			{
				new CommandViewModel(ironPython, @"Images\1319526175_application-x-python.png"),
				new CommandViewModel(ironRuby, @"Images\1319526162_application-x-ruby.png"),
				new CommandViewModel(powerShell, @"Images\Windows_PowerShell_icon.png"),
				new CommandViewModel(razor, @"Images\razor-icon.png")
			};

			this.Languages = languages;
			this.SelectedLanguage = languages[0];

			var observables = new List<IDisposable>();

			foreach (var lang in languages)
			{
				observables.Add(Observable
					.FromEventPattern<ViewModelEventArgs<CommandViewModel>>(lang, "Clicked")
					.Subscribe(e => this.SelectedLanguage = e.EventArgs.ViewModel));
			}

			this._observables = observables;
		}

		/// <summary>
		/// Gets the test scripts.
		/// </summary>
		public ObservableCollection<ITestScript> TestScripts { get; private set; }

		/// <summary>
		/// Gets the test results.
		/// </summary>
		public ObservableCollection<TestResultViewModel> TestResults { get; private set; }

		/// <summary>
		/// Gets the learn atha.
		/// </summary>
		public CommandViewModel LearnAtha { get; private set; }

		/// <summary>
		/// Gets the open tests.
		/// </summary>
		public CommandViewModel OpenTests { get; private set; }

		/// <summary>
		/// Gets the open test.
		/// </summary>
		public CommandViewModel OpenTest { get; private set; }

		/// <summary>
		/// Gets the save test.
		/// </summary>
		public CommandViewModel SaveTest { get; private set; }

		/// <summary>
		/// Gets the execute test.
		/// </summary>
		public CommandViewModel ExecuteTest { get; private set; }

		/// <summary>
		/// Gets the languages.
		/// </summary>
		public CommandViewModel[] Languages { get; private set; }

		/// <summary>
		/// Gets or sets the selected test script.
		/// </summary>
		public ITestScript SelectedTestScript
		{
			get
			{
				return this._selectedTestScript;
			}

			set
			{
				if (value != this._selectedTestScript)
				{
					this.OnPropertyChanging(() => this.SelectedTestScript);
					this._selectedTestScript = value;
					this.OnPropertyChanged(() => this.SelectedTestScript);

					if (this.SelectedTestScript != null)
					{
						this.SavedFilePath = this.SelectedTestScript.Title;
						this.SelectedLanguage = this.Languages.First(lang => lang.Text == this.SelectedTestScript.Language);
						this.TestScript = this.SelectedTestScript.Content;
					}
				}
			}
		}

		/// <summary>
		/// Gets or sets the selected language.
		/// </summary>
		/// <value>
		/// The selected language.
		/// </value>
		public CommandViewModel SelectedLanguage
		{
			get
			{
				return this._selectedLanguage;
			}

			set
			{
				if (value != this._selectedLanguage)
				{
					this.OnPropertyChanging(() => this.SelectedLanguage);
					this._selectedLanguage = value;
					this.OnPropertyChanged(() => this.SelectedLanguage);
				}
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this instance is tutorial visible.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is tutorial visible; otherwise, <c>false</c>.
		/// </value>
		public bool IsTutorialVisible
		{
			get
			{
				return this._isTutorialVisible;
			}

			set
			{
				if (value != this._isTutorialVisible)
				{
					this.OnPropertyChanging(() => this.IsTutorialVisible);
					this.OnPropertyChanging(() => this.IsTestScriptVisible);
					this._isTutorialVisible = value;
					this.OnPropertyChanged(() => this.IsTutorialVisible);
					this.OnPropertyChanged(() => this.IsTestScriptVisible);
				}

				if (value)
				{
					this.ExecuteTest.ClickCommand.IsExecutable = false;
					this.SaveTest.ClickCommand.IsExecutable = false;
				}
				else
				{
					this.SaveTest.ClickCommand.IsExecutable = !string.IsNullOrWhiteSpace(this.TestScript);
					this.ExecuteTest.ClickCommand.IsExecutable = !string.IsNullOrWhiteSpace(this.TestScript);
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether this instance is test script visible.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is test script visible; otherwise, <c>false</c>.
		/// </value>
		public bool IsTestScriptVisible
		{
			get
			{
				return !this.IsTutorialVisible;
			}
		}

		/// <summary>
		/// Gets or sets the saved file path.
		/// </summary>
		/// <value>
		/// The saved file path.
		/// </value>
		public string SavedFilePath
		{
			get
			{
				return this._savedFilePath;
			}

			set
			{
				if (value != this._savedFilePath)
				{
					this.OnPropertyChanging(() => this.SavedFilePath);
					this._savedFilePath = value;
					this.OnPropertyChanged(() => this.SavedFilePath);
				}
			}
		}

		/// <summary>
		/// Gets or sets the test script.
		/// </summary>
		/// <value>
		/// The test script.
		/// </value>
		public string TestScript
		{
			get
			{
				return this._testScript;
			}

			set
			{
				if (value != this._testScript)
				{
					this.OnPropertyChanging(() => this.TestScript);
					this._testScript = value;
					this.OnPropertyChanged(() => this.TestScript);

					this.SaveTest.ClickCommand.IsExecutable = !string.IsNullOrWhiteSpace(this.TestScript);
					this.ExecuteTest.ClickCommand.IsExecutable = !string.IsNullOrWhiteSpace(this.TestScript);
				}
			}
		}

		/// <summary>
		/// Called when [learn atha].
		/// </summary>
		private void OnLearnAtha()
		{
			this.IsTutorialVisible = !this.IsTutorialVisible;
		}

		/// <summary>
		/// Called when [execute test].
		/// </summary>
		private void OnExecuteTest()
		{
			if (!string.IsNullOrWhiteSpace(this.TestScript))
			{
				this.TestResults.Clear();

				this._clientTestRunner.ExecuteTest(this.SelectedLanguage.Text, this.TestScript, this.TestResults);
			}
		}

		/// <summary>
		/// Called when [save test].
		/// </summary>
		private void OnSaveTest()
		{
			if (string.IsNullOrWhiteSpace(this.SavedFilePath))
			{
				this.SavedFilePath = this._getNewTestSaveAsPath();
			}

			if (string.IsNullOrWhiteSpace(this.SavedFilePath)) return;

			var scriptWriter = this._testScriptWriterFactory();
			var testScript = new BasicTestScript(this.SelectedLanguage.Text, this.SavedFilePath, this.TestScript);

			scriptWriter.SaveScript(testScript);
		}

		/// <summary>
		/// Called when [open tests].
		/// </summary>
		private void OnOpenTests()
		{
			this.IsTutorialVisible = false;
			this.TestScripts.Clear();

			var repository = this._testScriptsReaderFactory();

			repository.GetScripts(
				testScripts =>
				{
					foreach (var testScript in testScripts)
						this.TestScripts.Add(testScript);
				});

		}

		/// <summary>
		/// Called when [open test].
		/// </summary>
		private void OnOpenTest()
		{
			this.IsTutorialVisible = false;

			this.SavedFilePath = this._getExistingTestPath();

			if (string.IsNullOrWhiteSpace(this.SavedFilePath)) return;

			var repository = this._testScriptReaderFactory(this.SavedFilePath);

			repository.GetScript(
				testScript =>
				{
					this.SelectedLanguage = this.Languages.First(lang => lang.Text == testScript.Language);
					this.TestScript = testScript.Content;
				});
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (this._observables != null)
					foreach (var observable in this._observables)
						observable.Dispose();
			}
		}

		~MainViewModel()
		{
			this.Dispose(false);
		}
	}
}